﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using QTPlugin;
using Kawanon.QtHoney;

namespace Kawanon.Usability
{
	public class GestureProvider
	{
		private const uint WM_LBUTTONDOWN = 0x0201;
		private const uint WM_LBUTTONUP = 0x0202;
		private const uint WM_RBUTTONDOWN = 0x0204;
		private const uint WM_RBUTTONUP = 0x0205;
		private const uint WM_MOUSEMOVE = 0x0200;
		private const uint WM_CAPTURECHANGED = 0x0215;
		private const uint WM_CONTEXTMENU = 0x007B;
		private const uint WM_TIMER = 0x0113;
		private const uint WM_CLOSE = 0x0010;

		[DllImport("user32.dll")]
		static extern bool GetAsyncKeyState(System.Windows.Forms.Keys vKey);

		[DllImport("user32.dll", ExactSpelling = true)]
		static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, TimerProc lpTimerFunc);
		delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);

		[DllImport("user32.dll", ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);

		private const int D_NORTH = 0;
		private const int D_EAST = 1;
		private const int D_SOUTH = 2;
		private const int D_WEST = 3;

		private const int MIN_DISTANCE = 40;
		private const int MAX_DEFER_TICKS = 1000 * 10000;
		private const int DEFER_PIXEL = 2;

		private struct GestureInput
		{
			public int Distance;
			public bool Ignored;
		}

		private bool m_inCasting = false;
		private Point m_previousPos;
		private GestureInput[] m_inputs;	// = new GestureInput[4];
		private IntPtr m_timerId = IntPtr.Zero;
		private int m_trailStage = 0;
		private List<GestureExpression> m_suggestions = new List<GestureExpression>();
		private bool m_bCancelContextMenu = false;
		private List<int> m_trails = new List<int>();
		private Point m_ptOrigin;
		private long m_deferedTicks;

		public IEnumerable<GestureExpression> Expressions { get; set; }
		public bool AllowUnknownExpression { get; set; }

		public GestureProvider()
		{
			//m_exprs.Add(new GestureExpression(new int[] { D_WEST }, GestureAction.From(Commands.GoBack, 1)));
			//m_exprs.Add(new GestureExpression(new int[] { D_EAST }, GestureAction.From(Commands.GoForward, 1)));
			//m_exprs.Add(new GestureExpression(new int[] { D_NORTH, D_WEST }, GestureAction.From(Commands.UndoClose)));
			//m_exprs.Add(new GestureExpression(new int[] { D_SOUTH, D_EAST }, GestureAction.From(Commands.ToggleTopMost)));
			// 無効な設定は省く
			//m_exprs.AddRange(GestureSettings.GetDefaultExpressions());
		}

		~GestureProvider()
		{
			EndGesture();
		}

		public event GestureEventHandler GestureDetected;
		public event GestureEventHandler TrailDetected;
		public event MouseEventHandler RightClick;

		private void TrackGestureExpression(int direction)
		{
			if (m_trailStage == 0)
			{
				return;
			}
			for (int i = m_suggestions.Count - 1; i >= 0; i--)
			{
				GestureExpression expr = m_suggestions[i];
				if (expr.Trails.Length < m_trailStage || expr.Trails[m_trailStage - 1] != direction)
				{
					m_suggestions.RemoveAt(i);
				}
			}
		}

		private GestureExpression TrackGestureExpression()
		{
			for (int i = m_suggestions.Count - 1; i >= 0; i--)
			{
				GestureExpression expr = m_suggestions[i];
				if (expr.Trails.Length != m_trailStage)	// 軌跡数が一致しない
				{
					m_suggestions.RemoveAt(i);
				}
			}
			if (m_suggestions.Count == 1)
			{
				return m_suggestions[0];
			}
			else if (m_suggestions.Count == 0)
			{
				return null;
			}
			else
			{
				Dbg.Abort("TrackGestureExpression: Duplication");
				return null;
			}
		}

		private void ResetGesture()
		{
			//for (int i = 0; i < m_inputs.Length; i++)
			//{
			//    m_inputs[i] = new GestureInput();
			//}
			m_inputs = new GestureInput[4];
		}

		private void RecognizeGesture(int direction)
		{
			GestureInput input = m_inputs[direction];
			if (!input.Ignored && input.Distance >= MIN_DISTANCE)
			{
				ResetGesture();

				//Console.WriteLine(direction);

				m_inputs[direction].Ignored = true;	// 構造体なのでinputは無意味
				m_trails.Add(direction);
				m_trailStage++;

				TrackGestureExpression(direction);

				if (TrailDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs();
					e.Expression = new GestureExpression(m_trails.ToArray(), new Usability.Actions.Pending());
					e.Suggestions = m_suggestions.AsReadOnly();
					TrailDetected.Invoke(this, e);
				}
			}
		}

		private void ProcessGesture()
		{
			int disX = m_previousPos.X - Cursor.Position.X;
			int disY = m_previousPos.Y - Cursor.Position.Y;

			m_previousPos = Cursor.Position;

			if (Math.Abs(disX) > Math.Abs(disY))
			{
				if (disX > 0)
				{
					m_inputs[D_WEST].Distance += disX;
					m_inputs[D_EAST].Distance = 0;

					RecognizeGesture(D_WEST);
				}
				else if (disX < 0)
				{
					m_inputs[D_EAST].Distance += -disX;
					m_inputs[D_WEST].Distance = 0;

					RecognizeGesture(D_EAST);
				}
			}
			else
			{
				if (disY > 0)
				{
					m_inputs[D_NORTH].Distance += disY;
					m_inputs[D_SOUTH].Distance = 0;

					RecognizeGesture(D_NORTH);
				}
				else
				{
					m_inputs[D_SOUTH].Distance += -disY;
					m_inputs[D_NORTH].Distance = 0;

					RecognizeGesture(D_SOUTH);
				}
			}
		}

		private void RaiseGestureDetected(GestureExpression expr)
		{
			if (expr != null)
			{
				if (GestureDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs();
					e.Expression = expr;
					GestureDetected.Invoke(this, e);
				}

				m_bCancelContextMenu = true;	// コマンドを実行したので、メニューはキャンセル
			}
			else if (AllowUnknownExpression)
			{
				if (m_trails.Count == 0)
				{
					return;
				}
				if (GestureDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs();
					e.Expression = new GestureExpression(m_trails.ToArray(), new Usability.Actions.Unknown());
					GestureDetected.Invoke(this, e);
				}

				m_bCancelContextMenu = true;
			}
		}

		private void EndGesture()
		{
			if (!m_inCasting)
			{
				return;
			}
			GestureExpression expr = TrackGestureExpression();

			m_inCasting = false;
			m_trailStage = 0;

			if (m_timerId != IntPtr.Zero)
			{
				if (!KillTimer(IntPtr.Zero, m_timerId))
				{
					Dbg.Abort("KillTimer");
				}
				m_timerId = IntPtr.Zero;
			}

			RaiseGestureDetected(expr);
		}

		//private void CancelGesture()
		//{

		//    m_suggestions.Clear();

		//    EndGesture();
		//}

		private void GestureTimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime)
		{
			//Console.WriteLine("{0} {1} {2}", hWnd, uMsg, nIDEvent);

			if (m_inCasting)
			{
				// キャプチャはできないので、コントロール外に出たら無効に...
				if (GetAsyncKeyState(Keys.LButton) || GetAsyncKeyState(Keys.RButton))
				{
					ProcessGesture();
					return;
				}
			}

			EndGesture();
		}

		private void BeginGesture()
		{
			m_inCasting = true;
			m_previousPos = Cursor.Position;
			// タイマ割り込みでおかしなことになる？
			m_timerId = SetTimer(IntPtr.Zero, IntPtr.Zero, 50, new TimerProc(GestureTimerProc));
			if (m_timerId == IntPtr.Zero)
			{
				Dbg.Abort("SetTimer");
			}
			m_trails.Clear();
			m_suggestions.Clear();
			m_suggestions.AddRange(Expressions);
			m_bCancelContextMenu = false;

			ResetGesture();
		}

		private bool MaybeRightClick()
		{
			if (m_deferedTicks != 0 && DateTime.Now.Ticks - m_deferedTicks <= MAX_DEFER_TICKS)
			{
				Point pt = Cursor.Position;
				pt.Offset(-DEFER_PIXEL, -DEFER_PIXEL);
				Rectangle rc = new Rectangle(pt, new Size(DEFER_PIXEL * 2 + 1, DEFER_PIXEL * 2 + 1));
				if (rc.Contains(m_ptOrigin))
				{
					if (RightClick != null)
					{
						MouseEventArgs e = new MouseEventArgs(MouseButtons.Right, 1, m_ptOrigin.X, m_ptOrigin.Y, 0);
						RightClick.Invoke(this, e);
					}
					return true;
				}
				m_deferedTicks = 0;
			}
			return false;
		}

		public bool ProcessMessage(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				case WM_RBUTTONDOWN:
					if (m_inCasting)
					{
						break;	//レアケース
					}
					BeginGesture();

					//NOTE: すぐにボタンが離された場合、右クリックをエミュレートさせる
					m_deferedTicks = DateTime.Now.Ticks;
					m_ptOrigin = Cursor.Position;
					return true;

				case WM_RBUTTONUP:
					EndGesture();

					if (MaybeRightClick())
					{
						return true;
					}
					break;

				case WM_CONTEXTMENU:
					if (m_inCasting && m_trailStage > 0)
					{
						EndGesture();
						return true;	// 右クリックメニューを出さない
					}
					if (m_bCancelContextMenu)
					{
						m_bCancelContextMenu = false;
						return true;
					}
					EndGesture();

					if (MaybeRightClick())
					{
						return true;
					}
					break;
			}
			return false;
		}

	}
}
